/***************************************************************************
 *   Copyright (C) 2005 - 2006 by                                          *
 *      Christian Muehlhaeuser, Last.fm Ltd <chris@last.fm>                *
 *      Erik Jaelevik, Last.fm Ltd <erik@last.fm>                          *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Steet, Fifth Floor, Boston, MA  02111-1307, USA.          *
 ***************************************************************************/

#include "updateinfogetter.h"
#include "Loqqer.h"
#include "utils.h"
#include "winutils.h"
#include "container.h"
#include "containerutils.h"
#include "lastfmapplication.h"

#include <QtCore>

#include <sstream>

using namespace std;

/******************************************************************************
    UpdateInfoGetter
******************************************************************************/
CUpdateInfoGetter::CUpdateInfoGetter() :
    mExtApp(NULL),
    mExtPlugins(NULL),
    mGetId(0),
    mbCancelled(false),
    mHttp(NULL)
{
    //mDowner.Attach(this);
/*
    connect(&mHttp, SIGNAL(requestFinished(int, bool)),
            this,   SLOT(downloadFinished(int, bool)));

    connect(&mHttp, SIGNAL(dataReadProgress(int, int)),
            this,   SIGNAL(progressMade(int, int)));

    connect(&mHttp, SIGNAL(stateChanged(int)),
            this,   SLOT(statusChanged(int)));
*/
}

/******************************************************************************
    ~UpdateInfoGetter
******************************************************************************/
CUpdateInfoGetter::~CUpdateInfoGetter()
{
    //mDowner.Detach(this);
}

/******************************************************************************
    GetAppInfo
******************************************************************************/
void
CUpdateInfoGetter::GetUpdateInfo(
    CComponentInfo*           appInfo,
    std::vector<CPluginInfo>* vecPluginInfo,
    bool                      bForceRefresh)
{
    mbCancelled = false;

    if (bForceRefresh || mApp.GetName() == "")
    {
        DownloadInfo();
        mExtApp = appInfo;
        mExtPlugins = vecPluginInfo;
    }
    else
    {
        // Copy our cached stuff
        *appInfo = mApp;
        *vecPluginInfo = mPlugins;
        emit updateInfoDone(false, "");
    }
}

/******************************************************************************
    GetPluginInfo
******************************************************************************/
/*
void
CUpdateInfoGetter::GetPluginInfo(
    std::vector<CPluginInfo>& vecInfo,
    bool                      bForceRefresh)
{
    mbCancelled = false;

    if (bForceRefresh || mPlugins.size() == 0)
    {
        DownloadInfo();
        mExtPlugins = &vecInfo;
    }
    else
    {
        // Copy our cached plugins
        vecInfo = mPlugins;
        emit pluginInfoDone(false, "");
    }
}
*/
/******************************************************************************
    Cancel
******************************************************************************/
void
CUpdateInfoGetter::Cancel()
{
    mbCancelled = true;

    if ( mHttp != NULL )
    {
        mHttp->abort();
    }
}

/******************************************************************************
    DownloadInfo
******************************************************************************/
void
CUpdateInfoGetter::DownloadInfo()
{
    #ifdef WIN32
        QString platform = "win";
    #elif defined Q_WS_X11
        QString platform = "linux";
    #else
        QString platform = "mac";
    #endif

    QString version = settingsService()->version();
    QString user = settingsService()->currentUsername();
    QString host = webService()->baseHost();
    LastFmApplication* app = qobject_cast<LastFmApplication*>( QApplication::instance() );

    QString path = QString( "/ass/upgrade.php?platform=%1&version=%2&lang=%3&user=%4" )
        .arg( platform )
        .arg( version )
        .arg( app->languageCode() )
        .arg( QString( QUrl::toPercentEncoding( user ) ) );

    // Must use a pointer here or it won't work (bug that happened on Spencer's laptop)
    mHttp = new Http( host, 80, this );
    mGetId = mHttp->get( path );

    connect(mHttp, SIGNAL(requestFinished(int, bool)),
            this,  SLOT(downloadFinished(int, bool)));

    connect(mHttp, SIGNAL(dataReadProgress(int, int)),
            this,  SIGNAL(progressMade(int, int)));

    connect(mHttp, SIGNAL(stateChanged(int)),
            this,  SLOT(statusChanged(int)));

    connect(mHttp, SIGNAL(dataSendProgress(int, int)),
            this,  SLOT(sendProgress(int, int) ));

    connect(mHttp, SIGNAL(readyRead(const QHttpResponseHeader&)),
            this,  SLOT(readyRead(const QHttpResponseHeader&) ));

    connect(mHttp, SIGNAL(requestStarted(int)),
            this,  SLOT(requestStarted(int)));

    connect(mHttp, SIGNAL(responseHeaderReceived ( const QHttpResponseHeader & )),
            this,  SLOT(responseHeaderReceived ( const QHttpResponseHeader & )));

    LOGL(3, "Requesting update info from: " << host << path );
}

/******************************************************************************
    downloadFinished
******************************************************************************/
void
CUpdateInfoGetter::downloadFinished(
    int  requestId,
    bool error)
{
    LOGL(4, "");

    if (requestId != mGetId)
    {
        // Ignore the setHost (or any other) requestFinished
        LOGL(3, "Ignoring, wrong ID");

        return;
    }

    if (mbCancelled)
    {
        LOG(3, "Download of update/plugin info cancelled. Not emitting any signals.\n");
        mHttp->deleteLater();
        mHttp = NULL;
        return;
    }

    if (error)
    {
        LOG(1, "Download of update/plugin info failed. Error: " <<
              mHttp->error() << " - " << mHttp->errorString() << "\n");

        emit updateInfoDone(true, QString(tr("Info download failed: %1")).
            arg(mHttp->errorString()));
    }
    else
    {
        try
        {
            QString response(mHttp->readAll());
            ParseResponse(response.toStdString());

            // Copy app data into object given to us
            if (mExtApp != NULL)
            {
                *mExtApp = mApp;
                mExtApp = NULL;
            }

            // Copy plugin data into vector given to us
            if (mExtPlugins != NULL)
            {
                *mExtPlugins = mPlugins;
                mExtPlugins = NULL;
            }

            emit updateInfoDone(false, "");
        }
        catch (ConnectionException& e)
        {
            emit updateInfoDone(true, QString(tr("Info download failed: %1")).
                arg(e.what()));
        }
    }

    mHttp->deleteLater();
    mHttp = NULL;
}

/******************************************************************************
    ParseResponse
******************************************************************************/
void
CUpdateInfoGetter::ParseResponse(
    string sResponse)
{
    if (sResponse.size() == 0)
    {
        LOG(1, "The response size was 0 for request " << mHttp->currentRequest().path());
        throw ConnectionException(QT_TR_NOOP("Downloaded update info was empty."));
    }

    // Stick reply into a stringstream for parsing
    istringstream is(sResponse);

    // Put each line of reply into an info struct
    string sLine;
    vector<string> quoted;
    char cSection = 0;
    mPlugins.clear();
    while (!getline(is, sLine).fail()) // global getline
    {
        sLine = QString::fromStdString(sLine).trimmed().toStdString();

        // Ignore blank lines and comments
        if (sLine == "" || sLine.substr(0, 2) == "//") { continue; }

        // Is this a section header?
        if (sLine.substr(0, 1) == "[")
        {
            if (sLine == "[App]")     { cSection = 'A'; LOG(4, "Found [App]\n"); }
            if (sLine == "[Plugins]") { cSection = 'P'; LOG(4, "Found [Plugins]\n"); }
            continue;
        }

        if (cSection == 0)
        {
            //Q_ASSERT(false);
            LOG(1, "File didn't start with a [] section: " << mHttp->currentRequest().path());
            throw ConnectionException(QT_TR_NOOP("Downloaded update info corrupt."));
        }

        CUtils::ParseQuotedStrings(sLine, quoted);

        switch (cSection)
        {
            case 'A':
            {
                PopulateComponent(quoted, mApp);
            }
            break;

            case 'P':
            {
                CPluginInfo info;
                PopulatePlugin(quoted, info);
                mPlugins.push_back(info);
            }
            break;

        }

        quoted.clear();
    }

    LOG(3, "Got info for " << static_cast<int>(mPlugins.size()) << " plugins\n");
}

/******************************************************************************
    PopulateComponent
******************************************************************************/
void
CUpdateInfoGetter::PopulateComponent(
    vector<string>& vecStrings,
    CComponentInfo& info)
{
    if (vecStrings.size() != 5)
    {
        Q_ASSERT(false);
        throw ConnectionException(QT_TR_NOOP("Downloaded update info for "
            "component didn't contain the correct number of entries."));
    }

    info.Clear();

    //   0. Name
    //   1. Exe path relative to %Program Files
    //   2. Download URL
    //   3. Version
    //   4. Install args if it's a setup exe
    info.SetName(QString::fromStdString(vecStrings.at(0)));

    #ifdef WIN32
        string sPF = CWinUtils::GetProgramFilesPath();
        if (sPF == "")
        {
            // Do our best at faking it. Will at least work some of the time.
            LOG(1, "Couldn't get PF path so trying to fake it.");
            sPF = "C:\\Program Files\\";
        }
        info.SetPath(QString::fromStdString(sPF) +
                     QString::fromStdString(vecStrings.at(1)));
    #else // not WIN32
        info.SetPath(QString::fromStdString(vecStrings.at(1)));
    #endif // WIN32

    info.SetDownloadURL (QString::fromStdString(vecStrings.at(2)));
    info.SetVersion     (QString::fromStdString(vecStrings.at(3)));
    info.SetInstallArgs (QString::fromStdString(vecStrings.at(4)));
}

/******************************************************************************
    PopulatePlugin
******************************************************************************/
void
CUpdateInfoGetter::PopulatePlugin(
    vector<string>& vecStrings,
    CPluginInfo&    info)
{
    if (vecStrings.size() != 9)
    {
        Q_ASSERT(false);
        throw ConnectionException(QT_TR_NOOP("Downloaded update info for plugin "
            " didn't contain the correct number of entries."));
    }

    info.Clear();

    //   0. Player name
    //   1. Plugin ID
    //   2. Player exe path relative to %Program Files
    //   3. Plugin download URL
    //   4. Plugin version
    //   5. Plugin install dir relative to player's current dir
    //   6. Plugin install args if it's a setup exe
    //   7. Min required player version for this plugin (inclusive range, leave empty for no restriction)
    //   8. Max required player version for this plugin (inclusive range, leave empty for no restriction)
    info.SetName        (QString::fromStdString(vecStrings.at(0)));
    info.SetPlayerName  (QString::fromStdString(vecStrings.at(0)));
    info.SetId          (QString::fromStdString(vecStrings.at(1)));

    // Need to check if we already have a custom path in registry first
    SettingsServiceInterface* settings = settingsService();
    QString customPath = settings->pluginPlayerPath(info.GetId());
    if (customPath.size() != 0)
    {
        info.SetPlayerPath(customPath);
    }
    else
    {
        #ifdef WIN32
            // Tack on Program Files dir to beginning of default path if on Win
            string sPF = CWinUtils::GetProgramFilesPath();
            if (sPF == "")
            {
                // Do our best at faking it. Will at least work some of the time.
                LOG(1, "Couldn't get PF path so trying to fake it.");
                sPF = "C:\\Program Files\\";
            }
            info.SetPlayerPath(QString::fromStdString(sPF) +
                               QString::fromStdString(vecStrings.at(2)));
        #else // not WIN32
            // Just write what we got for now, TODO properly
            info.SetPlayerPath(QString::fromStdString(vecStrings.at(2)));
        #endif // WIN32
    }

    info.SetDownloadURL (QString::fromStdString(vecStrings.at(3)));
    info.SetVersion     (QString::fromStdString(vecStrings.at(4)));
    info.SetInstallDir  (QString::fromStdString(vecStrings.at(5)));
    info.SetInstallArgs (QString::fromStdString(vecStrings.at(6)));
    info.SetPlayerVerMin(QString::fromStdString(vecStrings.at(7)));
    info.SetPlayerVerMax(QString::fromStdString(vecStrings.at(8)));
}

/******************************************************************************
    statusChanged
******************************************************************************/
void
CUpdateInfoGetter::statusChanged(
    int  state)
{
    //LOGL(3, "New QHttp state: " << state);

    QString msg = QHttpStateToString(state);
    emit statusChange(msg);
}
